﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HPAGMRestAPIWrapper
{
    /// <summary>
    /// Defect collection class
    /// </summary>
    public class AGMDefects : AGMEntityCollection<AGMDefect>
    {
        #region Constructor
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="connection">connection</param>
        public AGMDefects(AGMConnection connection)
            : base(connection)
        {
        }
        #endregion

        #region public methods
        /// <summary>
        /// Create Defect
        /// </summary>
        /// <param name="defectFields">Defect fields</param>
        /// <returns>Defect</returns>
        public override AGMDefect Create(List<AGMField> newDefectFields)
        {
            Common.SetDictionaryValue(ref newDefectFields, "detected-by", Connection.UserName);
            Common.SetDictionaryValue(ref newDefectFields, "creation-time", DateTime.UtcNow.ToString("yyyy-MM-dd"));
            Common.SetDictionaryValue(ref newDefectFields, "status", "New");

            AGMDefect newDefect = base.Create(newDefectFields);

            List<AGMField> backlogItemNewFields = new List<AGMField>();  
            Common.SetDictionaryValue(ref backlogItemNewFields, "entity-id", newDefect.Id.ToString());
            Common.SetDictionaryValue(ref backlogItemNewFields, "entity-name", (newDefect.Fields.Where(o => o.Name.Equals("name")).Single() as AGMField).Value);
            Common.SetDictionaryValue(ref backlogItemNewFields, "entity-type", "defect");
            Common.SetDictionaryValue(ref backlogItemNewFields, "status", "New");
            Common.SetDictionaryValue(ref backlogItemNewFields, "owner", Connection.UserName);            
            AGMReleaseBacklogItem newBacklogItem = new AGMReleaseBacklogItems(Connection).Create(backlogItemNewFields);

            newDefect.BacklogItem = newBacklogItem;

            return newDefect;
        }

        /// <summary>
        /// Search defects
        /// </summary>
        /// <param name="filterCondition">search condition</param>
        /// <returns></returns>
        public List<AGMDefect> Search(List<AGMField> filterCondition, List<AGMField> fields)
        {
            List<string> bugFields = new List<string>();
            List<string> backlogItemFields = new List<string>();

            List<AGMDefect> bugList = new List<AGMDefect>();
            AGMReleaseBacklogItems backlogItems = new AGMReleaseBacklogItems(Connection);
            List<AGMField> bugConditions = new List<AGMField>();
            List<AGMField> backlogItemConditions = new List<AGMField>();

            List<AGMDefect> bugResults = new List<AGMDefect>();
            List<AGMReleaseBacklogItem> backlogItemResults = new List<AGMReleaseBacklogItem>();

            #region Seperate requirement filters and backlog item filters
            foreach (AGMField filter in filterCondition)
            {
                if (Fields.Where(o => o.Name.Equals(filter.Name, StringComparison.InvariantCultureIgnoreCase) && o.Entity.Equals(filter.Entity, StringComparison.InvariantCultureIgnoreCase)).Count() == 1)
                {
                    bugConditions.Add(new AGMField { Name = filter.Name, Value = filter.Value });
                }
                else if (Fields.Where(o => o.Label.Equals(filter.Label, StringComparison.InvariantCultureIgnoreCase) && o.Entity.Equals(filter.Entity, StringComparison.InvariantCultureIgnoreCase)).Count() == 1)
                {
                    bugConditions.Add(new AGMField { Name = filter.Name, Value = filter.Value });
                }

                if (backlogItems.Fields.Where(o => o.Name.Equals(filter.Name, StringComparison.InvariantCultureIgnoreCase) && o.Entity.Equals(filter.Entity, StringComparison.InvariantCultureIgnoreCase)).Count() == 1)
                {
                    backlogItemConditions.Add(new AGMField { Name = filter.Name, Value = filter.Value });
                }
                else if (backlogItems.Fields.Where(o => o.Label.Equals(filter.Label, StringComparison.InvariantCultureIgnoreCase) && o.Entity.Equals(filter.Entity, StringComparison.InvariantCultureIgnoreCase)).Count() == 1)
                {
                    backlogItemConditions.Add(new AGMField { Name = filter.Name, Value = filter.Value });
                }
            }
            #endregion

            #region To get the filtered list of requirement and release backlog item.          
            if (!bugFields.Contains("id")) bugFields.Add("id");
            if (!bugFields.Contains("name")) bugFields.Add("name");
            if (!backlogItemFields.Contains("id")) backlogItemFields.Add("id");
            if (!backlogItemFields.Contains("entity-id")) backlogItemFields.Add("entity-id");

            #region Build view fields
            if (fields != null)
            {
                foreach (AGMField field in fields)
                {
                    if (Fields.Where(o => o.Name.Equals(field.Name, StringComparison.InvariantCultureIgnoreCase) && o.Entity.Equals(field.Entity, StringComparison.InvariantCultureIgnoreCase)).Count() == 1)
                    {
                        bugFields.Add(field.Name);
                    }
                    else if (Fields.Where(o => o.Label.Equals(field.Label, StringComparison.InvariantCultureIgnoreCase) && o.Entity.Equals(field.Entity, StringComparison.InvariantCultureIgnoreCase)).Count() == 1)
                    {
                        bugFields.Add(field.Name);
                    }

                    if (backlogItems.Fields.Where(o => o.Name.Equals(field.Name, StringComparison.InvariantCultureIgnoreCase) && o.Entity.Equals(field.Entity, StringComparison.InvariantCultureIgnoreCase)).Count() == 1)
                    {
                        backlogItemFields.Add(field.Name);
                    }
                    else if (backlogItems.Fields.Where(o => o.Label.Equals(field.Label, StringComparison.InvariantCultureIgnoreCase) && o.Entity.Equals(field.Entity, StringComparison.InvariantCultureIgnoreCase)).Count() == 1)
                    {
                        backlogItemFields.Add(field.Name);
                    }
                }
            }
            #endregion

            // Get defect collection
            if (bugConditions.Count > 0)
            { 
                bugResults = base.GetCollection(bugConditions, bugFields.ToArray()); 
            }
            // Get backlog item collection if backlog item condition exist.
            if (backlogItemConditions.Count > 0)
            {
                backlogItemConditions.Add(new AGMField { Name = "entity-type", Value = "defect" });
                backlogItemResults = new AGMReleaseBacklogItems(Connection).GetCollection(backlogItemConditions, backlogItemFields.ToArray());
            }
            #endregion

            #region Get finial defectList
            // when get defect collection
            if (bugResults.Count > 0)
            {
                // check backlogItem condition is not empty and return collection, get the cross items between them.
                if (backlogItemConditions.Count > 0 && backlogItemResults.Count > 0)
                {
                    foreach (AGMDefect bug in bugResults)
                    {
                        if (backlogItemResults.Where(o => Convert.ToInt32(o.GetField("entity-id").Value) == bug.Id).Count() > 0)
                        {
                            bugList.Add(bug);
                        }
                    };
                }
                else if (backlogItemConditions.Count == 0) // when no backlogitem condition, return all bugs result
                {
                    bugList = bugResults;                    
                }
            }
            else if (backlogItemResults.Count > 0) // when bug does not return collection, but backlog item return collection
            {
                if (bugConditions.Count == 0) // when no bug conditions, return all bugs based on backlog item.
                {
                    foreach (AGMReleaseBacklogItem backlogItem in backlogItemResults)
                    {
                        int reqId = Convert.ToInt32(backlogItem.GetField("entity-id").Value);
                        var bug = base.Get(reqId, bugFields.ToArray());
                        bugList.Add(bug);
                    };
                }
            }
            #endregion

            return bugList;
        }

        #endregion

        #region private methods

        private AGMDefect ChangeStatus(int id, string status)
        {
            var fields = new List<AGMField>();
            
            fields.Add(new AGMField{ Name="status", Value=status});

            AGMDefects defects = new AGMDefects(Connection);
            AGMDefect defect = defects.Get(id);
            defect.BacklogItem.SetUpdateField("status", status);
            defect.SetUpdateField("status", status);

            return defect.Update();
        }
        #endregion
    }

    /// <summary>
    /// Defect class
    /// </summary>
    public class AGMDefect: AGMEntity
    {
        #region private members
        #endregion

        #region public Properties
        public AGMReleaseBacklogItem BacklogItem { get; set; }
        #endregion

        #region constructor

        #endregion

        #region public methods
        /// <summary>
        /// Update defect
        /// </summary>
        /// <returns></returns>
        public AGMDefect Update()
        {
            if (Id <= 0) throw new Exception("Bug Id cannot be empty.");

            AGMDefects defects = new AGMDefects(Connection);
            AGMDefect updatedDefect = defects.Update(Id.Value, GetFieldsDictionary());
            updatedDefect.BacklogItem.Update();

            return updatedDefect;
        }

        /// <summary>
        /// Open defect
        /// </summary>
        /// <returns></returns>
        public AGMDefect Open()
        {
            SetUpdateField("status", "Open");
            BacklogItem.SetUpdateField("status", "In Progress");

            return Update();
        }

        /// <summary>
        /// Fix defect
        /// </summary>
        /// <returns></returns>
        public AGMDefect Fix()
        {
            SetUpdateField("status", "Fixed");
            BacklogItem.SetUpdateField("status", "In Testing");
            return Update();
        }

        /// <summary>
        /// Close defect
        /// </summary>
        /// <returns></returns>
        public AGMDefect Close()
        {
            SetUpdateField("status", "Closed");
            BacklogItem.SetUpdateField("status", "Done");
            return Update();
        }

        /// <summary>
        /// Proposeclose defect
        /// </summary>
        /// <returns></returns>
        public AGMDefect ProposeClose()
        {
            SetUpdateField("status", "Propose");
            BacklogItem.SetUpdateField("status", "Done");
            return Update();
        }

        #region Links in defect
        /// <summary>
        /// link to defects
        /// </summary>
        /// <param name="defectIds">defect ids</param>
        public void LinkToDefects(int[] defectIds)
        {
            if (defectIds == null)
                throw new Exception("Defect Ids are empty.");

            var defectlinks = new AGMDefectLinks(Connection);

            foreach (var defectId in defectIds)
            {
                defectlinks.Create(defectId, Id.Value, "defect");
            }
        }

        /// <summary>
        /// link to requirements
        /// </summary>
        /// <param name="reqIds">requirement Ids</param>
        public void LinkToRequirements(int[] reqIds)
        {
            if (reqIds == null)
                throw new Exception("Requirements Ids are empty.");

            var defectlinks = new AGMDefectLinks(Connection);

            foreach (var reqId in reqIds)
            {
                defectlinks.Create(Id.Value, reqId, "requirement");
            }
        }

        /// <summary>
        /// Get linked defects
        /// </summary>
        /// <returns>defect list</returns>
        public List<AGMDefect> GetLinkedDefects()
        {
            var linkeddefects = new List<AGMDefect>();

            var defectlinks = new AGMDefectLinks(Connection);
            var filters = new List<AGMField>();

            filters.Add(new AGMField{ Name="second-endpoint-id", Value=Id.Value.ToString()});
            filters.Add(new AGMField{ Name="second-endpoint-type", Value="defect"});

            var dllist = defectlinks.GetCollection(filters);

            if (dllist != null)
            {
                var defects = new AGMDefects(Connection);
                linkeddefects.AddRange(dllist.Select(dl => defects.Get(Convert.ToInt32(dl.GetField("second-endpoint-id").Value))).Where(d => d != null));
            }

            return linkeddefects;
        }

        /// <summary>
        /// Get linked requirements
        /// </summary>
        /// <returns>requirement list</returns>
        public List<AGMRequirement> GetLinkedRequirements()
        {
            var linkedrequirements = new List<AGMRequirement>();

            var defectlinks = new AGMDefectLinks(Connection);
            var filters = new List<AGMField>();

            filters.Add(new AGMField{Name="first-endpoint-id",Value=Id.Value.ToString()});
            filters.Add(new AGMField{Name="second-endpoint-type",Value="requirement"});

            var dllist = defectlinks.GetCollection(filters);

            if (dllist != null)
            {
                var reqs = new AGMRequirements(Connection);
                linkedrequirements.AddRange(dllist.Select(dl => reqs.Get(Convert.ToInt32(dl.GetField("second-endpoint-id").Value))).Where(d => d != null));
            }

            return linkedrequirements;
        }

        /// <summary>
        /// Delete defect links
        /// </summary>
        /// <param name="defectIds">defect ids</param>
        public void DeleteDefectLinks(int[] defectIds)
        {
            if (defectIds == null)
                throw new Exception("Defect Ids are empty.");

            var defectlinks = new AGMDefectLinks(Connection);

            foreach (var dl in
                defectIds.Select(defectId => defectlinks.Get(defectId, Id.Value, "defect")).Where(dl => dl != null))
            {
                dl.Delete();
            }
        }

        /// <summary>
        /// delete requirement links
        /// </summary>
        /// <param name="reqIds">requirement ids</param>
        public void DeleteRequirementLinks(int[] reqIds)
        {
            if (reqIds == null)
                throw new Exception("Requirement Ids are empty.");

            var defectlinks = new AGMDefectLinks(Connection);

            foreach (var dl in
                reqIds.Select(reqId => defectlinks.Get(Id.Value, reqId, "requirement")).Where(dl => dl != null))
            {
                dl.Delete();
            }
        }
        #endregion

        public void GetReleaseBacklogItem(string[] fields)
        {
            AGMReleaseBacklogItems backlogItems = new AGMReleaseBacklogItems(Connection);
            List<AGMField> filters = new List<AGMField>();
            filters.Add(new AGMField { Name = "entity-id", Value = Id.Value.ToString() });
            List<AGMReleaseBacklogItem> items = backlogItems.GetCollection(filters, fields);
            if (items != null)
            {
                BacklogItem = items[0];
            }
        }

        #endregion
    }
}
